﻿using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Practices.Prism.Regions;
using UltimateNavigationFramework.Extensions;
using UltimateNavigationFramework.Loaders;

namespace UltimateNavigationFramework.UriNavigation
{
    public class UltimateNavigationFrameLoadingService : IUltimateNavigationFrameLoadingService
    {
        #region Private members
        private IRegionManager _regionManager;
        private IUltimateNavigationJournal _journal;
        private IUltimateViewLoadingService _viewLoadingService;
        private UltimateNavigationFailedEventHandler _navigationFailed;
        private NavigatingCancelEventHandler _navigating;
        private NavigatedEventHandler _navigated;
        private NavigationStoppedEventHandler _navigationStopped;
        private bool _journalIsAddingHistoryPoint;
        private bool _ignoreStateChange;
        private Uri _currentSource;
        #endregion

        #region Navigation Properties
        public Uri CurrentSource
        {
            get
            {
                return this._currentSource;
            }
            internal set
            {
                this._currentSource = value;
            }
        }

        public bool CanGoForward
        {
            get
            {
                return this._journal.CanGoForward;
            }
        }

        public bool CanGoBack
        {
            get
            {
                return this._journal.CanGoBack;
            }
        } 
        #endregion

        #region Events
        public event UltimateNavigationFailedEventHandler NavigationFailed
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this._navigationFailed = this._navigationFailed + value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this._navigationFailed = this._navigationFailed - value;
            }
        }

        public event NavigatingCancelEventHandler Navigating
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this._navigating = this._navigating + value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this._navigating = this._navigating - value;
            }
        }

        public event NavigatedEventHandler Navigated
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this._navigated = this._navigated + value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this._navigated = this._navigated - value;
            }
        }

        public event NavigationStoppedEventHandler NavigationStopped
        {
            [MethodImpl(MethodImplOptions.Synchronized)]
            add
            {
                this._navigationStopped = this._navigationStopped + value;
            }
            [MethodImpl(MethodImplOptions.Synchronized)]
            remove
            {
                this._navigationStopped = this._navigationStopped - value;
            }
        }  
        #endregion  

        #region Properties
        public IUltimateNavigationJournal Journal
        {
            get
            {
                return this._journal;
            }
        } 
        #endregion

        #region Constructor
        public UltimateNavigationFrameLoadingService(IRegionManager regionManager, IUltimateViewLoadingService viewLoadingService)
        {
            this._regionManager = regionManager;
            this._viewLoadingService = viewLoadingService;
            this.InitializeJournal();
        } 
        #endregion

        #region Initialize Journal
        /// <summary>
        /// Initialises the journal object, attaches the navigated event
        /// </summary>
        private void InitializeJournal()
        {
            IUltimateNavigationJournal journal = this._journal;

            this._journal = new UltimateNavigationJournal();

            if (journal != null)
                journal.Navigated -= new EventHandler<UltimateNavigationJournalEventArgs>(this.Journal_Navigated);

            this._journal.Navigated += new EventHandler<UltimateNavigationJournalEventArgs>(this.Journal_Navigated);
        } 
        #endregion

        #region Journal Navigated
        /// <summary>
        /// Journal Navigated Event
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="args"></param>
        private void Journal_Navigated(object sender, UltimateNavigationJournalEventArgs args)
        {
            if (this._journalIsAddingHistoryPoint)
                return;

            //TODO: Do nothing for now
        }
        #endregion

        #region Go Forward / Back
        /// <summary>
        /// Navigate Forward
        /// </summary>
        /// <param name="host"></param>
        public void GoForward(UltimateNavigationFrame host)
        {
            this.GoForwardBackCore(NavigationMode.Forward, (this.CanGoForward ? 1 : 0) != 0, this.Journal.ForwardStack, 
                string.Format((IFormatProvider)CultureInfo.InvariantCulture, "Cannot go forward", new object[1]
              {
                    (object) "CanGoForward"
              }), host);
        }

        /// <summary>
        /// Navigate Backwards
        /// </summary>
        /// <param name="host"></param>
        public void GoBack(UltimateNavigationFrame host)
        {
            this.GoForwardBackCore(NavigationMode.Back, (this.CanGoBack ? 1 : 0) != 0, this.Journal.BackStack, 
                string.Format((IFormatProvider)CultureInfo.InvariantCulture, "Cannot go back", new object[1]
              {
                    (object) "CanGoBack"
              }), host);
        }

        private void GoForwardBackCore(
            NavigationMode mode, 
            bool canDoIt,
            Stack<UltimateNavigationJournalEntry> entries, 
            string onFailureText,
            UltimateNavigationFrame host)
        {
            if (canDoIt)
            {
                UltimateNavigationJournalEntry journalEntry = entries.Peek();

                bool isFragmentNavigationOnly = UltimateNavigationFramework.Helpers.UriParser.InternalUriIsFragment(journalEntry.Source) ||
                    UltimateNavigationFramework.Helpers.UriParser.InternalUriGetAllButFragment(journalEntry.Source) ==
                        UltimateNavigationFramework.Helpers.UriParser.InternalUriGetAllButFragment(this.CurrentSource); //TODO: Check current source

                this.RaiseNavigating(journalEntry.Source, mode, isFragmentNavigationOnly, host.Content as UltimateNavigationFrameContent,
                     (result) =>
                     {
                         if(!result)
                         {
                            if (mode == NavigationMode.Back)
                                this.Journal.GoBack();
                            else
                                this.Journal.GoForward();
                         }
                         else
                         {
                            this.RaiseNavigationStopped((object)null, journalEntry.Source);
                         }
                     });                    
            }
            else
            {
                Exception exception = (Exception)new InvalidOperationException(onFailureText);
                if (this.RaiseNavigationFailed((Uri)null, exception))
                    throw exception;
            }
        }
        #endregion

        #region Navigate Url
        /// <summary>
        /// Navigates to the particular uri
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="regionName"></param>
        /// <param name="host"></param>
        public void NavigateUrl(string uri, string regionName, UltimateNavigationFrame host, bool suppressJournalAdd)
        {
            try
            {
                Uri mappedUri = null;
                Uri actualUri = new Uri(uri, UriKind.Relative);

                if (!host.Dispatcher.CheckAccess())
                {
                    host.Dispatcher.BeginInvoke((Action)(() => this.NavigateUrl(uri, regionName, host, suppressJournalAdd)));
                    return;
                }
                else if (!_ignoreStateChange)
                {
                    if (!string.IsNullOrEmpty(uri) && !string.IsNullOrEmpty(regionName) && host != null)
                    {
                        this.CheckAuthorisation(new Uri(uri, UriKind.Relative), host, regionName, suppressJournalAdd, () =>
                            {
                                // Gets the current region to inject the view into
                                var region = this._regionManager.Regions[regionName];

                                UltimateNavigationFrameContent existingContentPage = host.Content as UltimateNavigationFrameContent;
                                UltimateNavigationFrameContent newContentPage = null;

                                bool isChild = false;

                                // Map the uri object
                                UltimateNavigationUriMapper uriMapper = host.UriMapper;
                                if (uriMapper != null)
                                {
                                    foreach (var mappings in uriMapper.UriMappings)
                                    {
                                        var mapped = mappings.MapUri(actualUri);
                                        if (mapped != null)
                                        {
                                            if (mappings.IsChildFrame)
                                                isChild = true;

                                            mappedUri = mapped;
                                            break;
                                        }
                                    }
                                }

                                if (mappedUri != null)
                                {
                                    // Gets the actual view
                                    this.LoadContent(region, new Microsoft.Practices.Prism.Regions.NavigationContext(null, mappedUri), (viewObject, success, error) =>
                                    {
                                        try
                                        {
                                            if (success)
                                            {
                                                var view = viewObject;

                                                // Wraps the view in a navigation content object
                                                var effectiveView = this.GetRegionContent(view);

                                                string title = string.Empty;
                                                if (effectiveView is UltimateNavigationFrameContent)
                                                {
                                                    newContentPage = effectiveView as UltimateNavigationFrameContent;
                                                    title = newContentPage.Title;

                                                    this.RaiseNavigating(mappedUri, NavigationMode.New, false, existingContentPage, (result) =>
                                                    {
                                                        if (result)
                                                        {
                                                            // Need to rollback url if u cannot navigate away
                                                            this.RollbackUri();
                                                            this.RaiseNavigationStopped((object)null, mappedUri);
                                                            return;
                                                        }

                                                        // Raise Navigated so view can be populated to region
                                                        this.RaiseNavigated((object)effectiveView, mappedUri, existingContentPage, newContentPage);

                                                        // Activate the view in the region
                                                        this.LoadViewToRegion(regionName, effectiveView);

                                                        // TODO: To confirm
                                                        this.CurrentSource = actualUri;

                                                        // Sets the host's navigation properties
                                                        host.CurrentSource = this.CurrentSource;
                                                        host.CanGoBack = this.CanGoBack;
                                                        host.CanGoForward = this.CanGoForward;

                                                        if (!isChild)
                                                        {
                                                            _journalIsAddingHistoryPoint = true;
                                                            this._journal.AddHistoryPointIfDifferent(new UltimateNavigationJournalEntry(title, actualUri), suppressJournalAdd);
                                                            _journalIsAddingHistoryPoint = false;
                                                        }
                                                        else
                                                        {
                                                            bool isAttached = false;
                                                            RoutedEventHandler navigateChildren = null;
                                                            navigateChildren = (sender, e) =>
                                                            {
                                                                host.Dispatcher.BeginInvoke(() =>
                                                                {
                                                                    // Supports parallel children frames
                                                                    var childrenFrames = host.FindFirstOccuringChildren<UltimateNavigationFrame>();
                                                                    if (childrenFrames != null && childrenFrames.Count() > 0)
                                                                    {
                                                                        foreach (var childFrame in childrenFrames)
                                                                            this.NavigateUrl(actualUri.OriginalString, childFrame.Title, childFrame, suppressJournalAdd);
                                                                    }
                                                                });

                                                                if (isAttached)
                                                                    newContentPage.Loaded -= navigateChildren;
                                                            };

                                                            // Will run on the ui thread as the child will only be found when the ui is loaded
                                                            if (!newContentPage.IsLoaded)
                                                            {
                                                                isAttached = true;
                                                                newContentPage.Loaded += navigateChildren;
                                                            }
                                                            else
                                                                navigateChildren(this, new RoutedEventArgs());
                                                        }
                                                    });
                                                }
                                                else
                                                    return;
                                            }
                                            else// If failed load content
                                            {
                                                // Throw to the catch block
                                                throw error;
                                            }
                                        }
                                        catch (Exception ex)
                                        {
                                            // Show error page here
                                            this.LoadErrorPage(new Uri(uri, UriKind.Relative), host, ex, regionName, suppressJournalAdd);

                                            // If is is handled then return else throw it
                                            if (!this.RaiseNavigationFailed(new Uri(uri, UriKind.Relative), ex))
                                                return;

                                            throw ex;
                                        }        
                                    }, host);
                                }
                                else
                                {
                                    // Throw error if uri is not mapped    
                                    throw new InvalidOperationException(
                                        string.Format(CultureInfo.CurrentCulture, "Invalid uri specified."), null);
                                }
                            });                        
                    }
                }
            }
            catch (Exception ex)
            {
                // Show error page here
                this.LoadErrorPage(new Uri(uri, UriKind.Relative), host, ex, regionName, suppressJournalAdd);

                // If is is handled then return else throw it
                if (!this.RaiseNavigationFailed(new Uri(uri, UriKind.Relative), ex))
                    return;

                throw ex;
            }
        }

        /// <summary>
        /// TODO: To revisit this and error page
        /// </summary>
        /// <param name="newTargetUri"></param>
        /// <param name="host"></param>
        /// <param name="regionName"></param>
        /// <param name="suppressJournalAdd"></param>
        private void CheckAuthorisation(Uri newTargetUri, UltimateNavigationFrame host, string regionName, bool suppressJournalAdd, Action authorisedCallback)
        {
            if (host.Authoriser != null)
            {
                try
                {
                    host.Authoriser.CheckAuthorization(newTargetUri);
                    authorisedCallback();
                }
                catch (UnauthorizedAccessException ex)
                {
                    var redirectUri = host.Authoriser.ErrorUri;

                    if (!string.IsNullOrWhiteSpace(redirectUri))
                    {
                        var unauthorisedUri = new Uri(redirectUri + String.Format("?ErrorUri={0}&ErrorMessage={1}", newTargetUri, ex.Message), UriKind.Relative);

                        // Raise navigating in the current host
                        this.RaiseNavigating(newTargetUri, NavigationMode.New, false, host.Content as UltimateNavigationFrameContent, (result) =>
                        {
                            // Prevent navigation is fired
                            if (result)
                            {
                                this.RollbackUri();
                            }
                            else
                            {
                                // Gets the current region to inject the view into
                                var region = this._regionManager.Regions[regionName];

                                this.LoadContent(region,
                                    new Microsoft.Practices.Prism.Regions.NavigationContext(null, unauthorisedUri),
                                    (viewObject, success, error) =>
                                    {
                                        if (success)
                                        {
                                            var unauthorisedUriView = viewObject;

                                            // Wrap the view in a navigation content object
                                            var effectiveUnauthorisedView = this.GetRegionContent(unauthorisedUriView);

                                            string title = string.Empty;
                                            if (effectiveUnauthorisedView is UltimateNavigationFrameContent)
                                            {
                                                var newUnauthorisedPage = effectiveUnauthorisedView as UltimateNavigationFrameContent;
                                                title = newUnauthorisedPage.Title;

                                                // Raise Navigated so view can be populated to region
                                                this.RaiseNavigated((object)effectiveUnauthorisedView, newTargetUri,
                                                    host.Content as UltimateNavigationFrameContent, newUnauthorisedPage);

                                                // Activate the view in the region
                                                this.LoadViewToRegion(regionName, effectiveUnauthorisedView);

                                                this.CurrentSource = newTargetUri;

                                                // Sets the host's navigation properties
                                                host.CurrentSource = this.CurrentSource;
                                                host.CanGoBack = this.CanGoBack;
                                                host.CanGoForward = this.CanGoForward;

                                                _journalIsAddingHistoryPoint = true;
                                                this._journal.AddHistoryPointIfDifferent(
                                                    new UltimateNavigationJournalEntry(title, newTargetUri), suppressJournalAdd);
                                                _journalIsAddingHistoryPoint = false;
                                            }
                                        }
                                        // else do nothing if error view is not found                                    
                                    }, host);
                            }
                        });
                    }
                    // Else do nothing if redirect uri is not specified
                }
            }
            else
                authorisedCallback();
            // Else we allow access if no authoriser is defined                                        
        }

        private void LoadErrorPage(Uri errorTarget, UltimateNavigationFrame host, Exception ex, string regionName, bool suppressJournalAdd)
        {
            if (!host.Dispatcher.CheckAccess())
            {
                host.Dispatcher.BeginInvoke((Action)(() => this.LoadErrorPage(errorTarget, host, ex, regionName, suppressJournalAdd)));
                return;
            }
            else
            {
                if (host.ErrorPages != null)
                {
                    var errorPage = (from ep in host.ErrorPages
                                     where ep.Matches(ex)
                                     select ep).FirstOrDefault();

                    // If error is a known type, then we load the error page
                    if (errorPage != null)
                    {
                        // Raise navigating in the current host
                        this.RaiseNavigating(errorTarget, NavigationMode.New, false, host.Content as UltimateNavigationFrameContent, (result) =>
                        {
                            // Prevent navigation is fired
                            if (result)
                            {
                                this.RollbackUri();
                            }
                            else
                            {
                                // Gets the current region to inject the view into
                                var region = this._regionManager.Regions[regionName];

                                this.LoadContent(region,
                                    new Microsoft.Practices.Prism.Regions.NavigationContext(null, errorPage.ErrorPageUri),
                                    (viewObject, success, error) =>
                                    {
                                        if (success)
                                        {
                                            var errorView = viewObject;

                                            // Wrap the view in a navigation content object
                                            var effectiveErrorView = this.GetRegionContent(errorView);

                                            string title = string.Empty;
                                            if (effectiveErrorView is UltimateNavigationFrameContent)
                                            {
                                                var newErrorPage = effectiveErrorView as UltimateNavigationFrameContent;
                                                title = newErrorPage.Title;

                                                var errorWithException = errorTarget.OriginalString;
                                                var exception = string.Format("exception={0}", ex.Message);
                                                var stacktrace = string.Format("stackTrace={0}", ex.StackTrace);
                                                var innerException = string.Format("innerException={0}", ex.InnerException != null
                                                                            ? ex.InnerException.Message
                                                                            : string.Empty);

                                                var query = string.Format("?{0}&{1}&{2}", exception, stacktrace,
                                                                              innerException);

                                                var errorUri = new Uri(errorWithException + query, UriKind.Relative);

                                                // Raise Navigated so view can be populated to region
                                                this.RaiseNavigated((object)effectiveErrorView, errorUri, host.Content as UltimateNavigationFrameContent, newErrorPage);

                                                // Activate the view in the region
                                                this.LoadViewToRegion(regionName, effectiveErrorView);

                                                this.CurrentSource = errorTarget;

                                                // Sets the host's navigation properties
                                                host.CurrentSource = this.CurrentSource;
                                                host.CanGoBack = this.CanGoBack;
                                                host.CanGoForward = this.CanGoForward;

                                                _journalIsAddingHistoryPoint = true;
                                                this._journal.AddHistoryPointIfDifferent(new UltimateNavigationJournalEntry(title, errorTarget), suppressJournalAdd);
                                                _journalIsAddingHistoryPoint = false;
                                            }
                                        }
                                        // else do nothing if error view is not found                                    
                                    }, host);
                            }
                        });
                    }
                }
                // Else we do nothing
            }            
        }

        /// <summary>
        /// Loads the content into the region
        /// </summary>
        /// <param name="region"></param>
        /// <param name="navigationContext"></param>
        /// <returns></returns>
        protected virtual void LoadContent(IRegion region, Microsoft.Practices.Prism.Regions.NavigationContext navigationContext,
            Action<object, bool, Exception> callback, UltimateNavigationFrame host)
        {
            if (region == null) throw new ArgumentNullException("region");
            if (navigationContext == null) throw new ArgumentNullException("navigationContext");
            
            string candidateTargetContract = this.GetContractFromUri(navigationContext.Uri);

            var candidates = this.GetCandidatesFromRegion(region, candidateTargetContract);

            var acceptingCandidates =
                candidates.Where(
                    v =>
                    {
                        var navigationAware = v as INavigationAware;
                        if (navigationAware != null && !navigationAware.IsNavigationTarget(navigationContext))
                        {
                            return false;
                        }
                        
                        var frameworkElement = v as FrameworkElement;
                        if (frameworkElement == null)
                        {
                            return true;
                        }

                        navigationAware = frameworkElement.DataContext as INavigationAware;
                        return navigationAware == null || navigationAware.IsNavigationTarget(navigationContext);
                    });

            var view = acceptingCandidates.FirstOrDefault();

            if (view != null)
            {
                callback(view, true, null);
            }
            else
            {
                _viewLoadingService.LoadView(navigationContext.Uri.OriginalString, (viewObject, success, exception) =>
                    {
                        if (success)
                            region.Add(viewObject);

                        callback(viewObject, success, exception);
                    }, host);                
            }

            //view = this.CreateNewRegionItem(candidateTargetContract);
            //region.Add(view);
            //return view;
        }

        ///// <summary>
        ///// Provides a new item for the region based on the supplied candidate target contract name.
        ///// </summary>
        ///// <param name="candidateTargetContract">The target contract to build.</param>
        ///// <returns>An instance of an item to put into the <see cref="IRegion"/>.</returns>
        //protected virtual object CreateNewRegionItem(string candidateTargetContract)
        //{
        //    object newRegionItem;
        //    try
        //    {
        //        newRegionItem = ServiceLocator.Current.GetInstance<object>(candidateTargetContract);
        //    }
        //    catch (ActivationException e)
        //    {
        //        throw new InvalidOperationException(
        //            string.Format(CultureInfo.CurrentCulture, "Cannot create navigation target", candidateTargetContract),
        //            e);
        //    }
        //    return newRegionItem;
        //}

        /// <summary>
        /// Returns the candidate TargetContract based on the <see cref="Uri"/>.
        /// </summary>
        /// <param name="context">The uri.</param>
        /// <returns>The candidate contract to seek within the <see cref="IRegion"/> and to use, if not found, when resolving from the container.</returns>
        protected virtual string GetContractFromUri(Uri uri)
        {
            if (uri == null) throw new ArgumentNullException("uri");

            var candidateTargetContract = UltimateNavigationFramework.Helpers.UriParser.EnsureAbsolute(uri).AbsolutePath;
            candidateTargetContract = candidateTargetContract.TrimStart('/');
            return candidateTargetContract;
        }

        /// <summary>
        /// Returns the set of candidates that may satisfiy this navigation request.
        /// </summary>
        /// <param name="region">The region containing items that may satisfy the navigation request.</param>
        /// <param name="candidateNavigationContract">The candidate navigation target as determined by <see cref="GetContractFromNavigationContext"/></param>
        /// <returns>An enumerable of candidate objects from the <see cref="IRegion"/></returns>
        protected virtual IEnumerable<object> GetCandidatesFromRegion(IRegion region, string candidateNavigationContract)
        {
            if (region == null) throw new ArgumentNullException("region");
            return region.Views.Where(v =>
                string.Equals(v.GetType().Name, candidateNavigationContract, StringComparison.Ordinal) ||
                string.Equals(v.GetType().FullName, candidateNavigationContract, StringComparison.Ordinal));
        }

        /// <summary>
        /// Creates the wrapper object should it not exist
        /// </summary>
        /// <param name="view"></param>
        /// <returns></returns>
        protected virtual DependencyObject GetRegionContent(Object view)
        {
            var frameworkElement = view as FrameworkElement;
            
            var titledPage = view as IPageTitle;

            // If page title does not exist on the page, then we look at the view model via the datacontext
            if (titledPage == null && frameworkElement != null)
            {
                titledPage = frameworkElement.DataContext as IPageTitle;
            }

            var content =
                frameworkElement
                ?? new ContentControl
                {
                    Content = view,
                    HorizontalAlignment = HorizontalAlignment.Stretch,
                    VerticalContentAlignment = VerticalAlignment.Stretch
                };

            if (content is UltimateNavigationFrameContent)            
                return content;            

            var contentWrapper =
                content.Parent
                ?? new UltimateNavigationFrameContent
                {
                    Content = content,
                    Title = titledPage != null ? titledPage.PageTitle : String.Empty
                };

            return contentWrapper;
        }

        /// <summary>
        /// Loads the view into the region
        /// </summary>
        /// <param name="regionName"></param>
        /// <param name="view"></param>
        protected virtual void LoadViewToRegion(string regionName, object view)
        {
            if (view != null)
            {
                // Checks if it exists in the region, if not then add it to region
                if (!this._regionManager.Regions[regionName].Views.Contains(view))
                    this._regionManager.AddToRegion(regionName, view);

                this._regionManager.Regions[regionName].Activate(view);
            }
            else
            {
                throw new Exception("Could not show null view to region " + regionName);
            }
        }
        #endregion

        #region Raise events
        /// <summary>
        /// Raise navigated event
        /// </summary>
        /// <param name="content"></param>
        /// <param name="uri"></param>
        /// <param name="existingContentPage"></param>
        /// <param name="newContentPage"></param>
        private void RaiseNavigated(object content, 
            Uri uri, 
            UltimateNavigationFrameContent existingContentPage, 
            UltimateNavigationFrameContent newContentPage)
        {
            NavigatedEventHandler navigatedEventHandler = this._navigated;
            NavigationEventArgs e = new NavigationEventArgs(content, uri);

            if (navigatedEventHandler != null)
            {                
                navigatedEventHandler((object)this, e);
            }

            if (existingContentPage != null && content != null)
            {
                var actualContent = content as UltimateNavigationFrameContent;
                if (actualContent != null)
                {
                    var childrenContents = actualContent.FindChildren<UltimateNavigationFrameContent>();
                    int count = 1;

                    if (childrenContents != null)
                        count += childrenContents.Count();

                    int currentIndex = 0;

                    // Loop through all visible children frames and fire navigate away
                    if (childrenContents != null)
                    {
                        foreach (var childContent in childrenContents)
                        {
                            childContent.InternalOnNavigatedFrom(e);
                        }
                    }
                }

                existingContentPage.InternalOnNavigatedFrom(e);
            }

            if (newContentPage == null)
                return;

            newContentPage.InternalOnNavigatedTo(e);
        }

        /// <summary>
        /// Raised Navigating event
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="mode"></param>
        /// <param name="isFragmentNavigationOnly"></param>
        /// <param name="host"></param>
        /// <returns></returns>
        private void RaiseNavigating(Uri uri, 
            NavigationMode mode, 
            bool isFragmentNavigationOnly,
            UltimateNavigationFrameContent content,
            Action<bool> continuationCallback)
        {
            NavigatingCancelEventHandler cancelEventHandler = this._navigating;
            NavigatingCancelEventArgs e = new NavigatingCancelEventArgs(uri, mode);

            if (cancelEventHandler != null)
            {                
                cancelEventHandler((object)this, e);
                if (continuationCallback != null && e.Cancel)
                {
                    continuationCallback(e.Cancel);
                    continuationCallback = null;
                }
            }

            if (!isFragmentNavigationOnly && content != null)
            {                
                var childrenContents = content.FindChildren<UltimateNavigationFrameContent>();
                int count = 1;

                if (childrenContents != null)
                    count += childrenContents.Count();

                int currentIndex = 0;

                Action<bool> delayedCallback = (b) =>
                    {
                        if (b)
                        {
                            e.Cancel = true;
                            if (continuationCallback != null)
                            {
                                continuationCallback(true);
                                continuationCallback = null;
                            }
                        }
                        else                        
                            ++currentIndex;                                                    

                        if (currentIndex == count)
                        {
                            if (continuationCallback != null)
                            {
                                continuationCallback(false);
                                continuationCallback = null;
                            }
                        }
                    };

                // Loop through all visible children frames and fire navigate away
                if (childrenContents != null)
                {
                    foreach (var childContent in childrenContents)
                    {
                        childContent.InternalOnNavigatingFrom(e, delayedCallback);
                    }
                }

                content.InternalOnNavigatingFrom(e, delayedCallback);
            }
            else
            {
                if (continuationCallback != null)
                {
                    continuationCallback(false);
                    continuationCallback = null;
                }
            }
        }

        /// <summary>
        /// Raise navigation failed event
        /// </summary>
        /// <param name="uri"></param>
        /// <param name="exception"></param>
        /// <returns></returns>
        private bool RaiseNavigationFailed(Uri uri, 
            Exception exception)
        {
            UltimateNavigationFailedEventHandler failedEventHandler = this._navigationFailed;
            UltimateNavigationFailedEventArgs e = new UltimateNavigationFailedEventArgs(uri, exception);

            if (failedEventHandler != null)
                failedEventHandler((object)this, e);

            return !e.Handled;
        }

        /// <summary>
        /// Raise navigation stopped event
        /// </summary>
        /// <param name="content"></param>
        /// <param name="uri"></param>
        private void RaiseNavigationStopped(object content, 
            Uri uri)
        {
            NavigationStoppedEventHandler stoppedEventHandler = this._navigationStopped;

            if (stoppedEventHandler == null)
                return;

            NavigationEventArgs e = new NavigationEventArgs(content, uri);

            stoppedEventHandler((object)this, e);
        }

        /// <summary>
        /// Rollback uri
        /// </summary>
        private void RollbackUri()
        {
            if (Application.Current.Host.NavigationState != this.CurrentSource.OriginalString)
            {
                _ignoreStateChange = true;
                Application.Current.Host.NavigationState = this.CurrentSource.OriginalString;
                _ignoreStateChange = false;
            }
        }
        #endregion        
    }
}
